#include "LXMysql.h"
#include <iostream>
#include <mysql/mysql.h>
namespace LX
{
    struct LXMysqlData
    {
        MYSQL *mysql = nullptr;
        MYSQL_RES *res = nullptr;
        MYSQL_ROW row = nullptr;
        unsigned long *lengths = nullptr;
        unsigned int col_count = 0;
        LXMysqlData() {}
        /**
         * @brief LXMysqlData 类的析构函数，负责释放数据库连接及结果集资源
         *
         * 本函数在对象销毁时自动调用，主要功能为：
         * 1. 关闭 MySQL 数据库连接
         * 2. 释放查询结果集内存
         * 3. 重置成员变量指针和计数器
         *
         * @note 参数说明：析构函数无参数
         * @note 返回值说明：析构函数无返回值
         */
        ~LXMysqlData()
        {
            // 关闭 MySQL 数据库连接并重置指针
            if (mysql)
            {
                mysql_close(mysql);
                mysql = nullptr;
            }

            // 释放查询结果集内存并重置指针
            if (res)
            {
                mysql_free_result(res);
                res = nullptr;
            }

            // 重置行数据指针、字段长度指针和列计数器
            row = nullptr;
            lengths = nullptr;
            col_count = 0;
        }
    };
    LXMysql::LXMysql()
        : m_pData(new LXMysqlData())
    {
    }
    LXMysql::~LXMysql()
    {
        if (m_pData)
        {
            delete m_pData;
            m_pData = nullptr;
        }
    }
    bool LXMysql::Init()
    {
        // 验证内部数据容器有效性
        if (!m_pData)
        {
            return false;
        }

        // 清理现有数据库连接
        Close();

        // 创建新的MySQL实例
        m_pData->mysql = mysql_init(nullptr);
        if (!m_pData->mysql)
            return false;
        mysql_set_character_set(m_pData->mysql, "utf8");
        return true;
    }
    void LXMysql::Close()
    {
        if (!m_pData)
            return;
        m_pData->~LXMysqlData();
        // return true;
    }
    /**
     * @brief 连接到MySQL数据库服务器
     *
     * @param ip 数据库服务器的IP地址或主机名
     * @param user 数据库登录用户名
     * @param pwd 数据库登录密码
     * @param dbname 要连接的默认数据库名称
     * @param port 数据库服务器的端口号
     * @param flag 客户端连接标志位（用于设置连接选项）
     * @return bool 连接成功返回true，失败返回false
     */
    bool LXMysql::Connect(const char *ip, const char *user, const char *pwd, const char *dbname, unsigned short port, unsigned long flag)
    {
        // 检查MySQL实例是否已初始化
        if (!m_pData || !m_pData->mysql)
            return false;

        // 尝试建立数据库连接，并验证连接结果
        auto mysql = mysql_real_connect(m_pData->mysql, ip, user, pwd, dbname, port, nullptr, flag);

        // 验证返回的连接句柄是否与当前实例匹配
        if (mysql != m_pData->mysql)
            return false;

        return true;
    }
    // 参数设置，需要在connect()之前调用
    bool LXMysql::SetOption(LX_OPT option, const char *value)
    {
        if (!m_pData || !m_pData->mysql)
            return false;
        return 0 == mysql_options(m_pData->mysql, (mysql_option)option, value);
    }
    void LXMysql::PrintError()
    {
        if (!m_pData->mysql)
        {
            std::cout << "mysql not init!" << std::endl;
            return;
        }
        std::cout << mysql_error(m_pData->mysql) << std::endl;
    }
    std::string LXMysql::GetErrorStr()
    {
        if (!m_pData->mysql)
        {
            return "mysql not init!";
        }
        return mysql_error(m_pData->mysql);
    }
    int LXMysql::SetTimeout(unsigned int read_timeout, unsigned int write_timeout)
    {
        if (!m_pData || !m_pData->mysql)
            return -1;
        if (mysql_options(m_pData->mysql, MYSQL_OPT_READ_TIMEOUT, &read_timeout))
        {
            return 1;
        }
        if (mysql_options(m_pData->mysql, MYSQL_OPT_WRITE_TIMEOUT, &write_timeout))
        {
            return 2;
        }
        return 0;
    }
    int LXMysql::SetConnectTimeout(unsigned int connect_timeout_src)
    {
        return SetOption(LX_OPT_CONNECT_TIMEOUT, (char *)&connect_timeout_src);
    }

    bool LXMysql::Next()
    {
        if (!m_pData || !m_pData->res)
            return false;
        m_pData->row = mysql_fetch_row(m_pData->res);
        if (!m_pData->row)
            return false;
        m_pData->lengths = mysql_fetch_lengths(m_pData->res);
        return true;
    }
    bool LXMysql::Exec(std::string sql, EXEC_TYPE type)
    {
        if (!m_pData || !m_pData->mysql)
            return false;
        if (mysql_real_query(m_pData->mysql, sql.data(), sql.size()))
            return false;
        FreeResult();
        if (type == EXEC_TYPE::EXEC_TYPE_USE)
            m_pData->res = mysql_use_result(m_pData->mysql);
        else
            m_pData->res = mysql_store_result(m_pData->mysql);
        m_pData->col_count = mysql_num_fields(m_pData->res);
        return m_pData->res;
    }
    bool LXMysql::Exec(const char *sql, size_t len, EXEC_TYPE type)
    {
        if (!m_pData || !m_pData->mysql || !sql)
            return false;
        int ret = 0;
        if (len <= 0)
            ret = mysql_query(m_pData->mysql, sql);
        else
            ret = mysql_real_query(m_pData->mysql, sql, len);
        if (ret)
            return false;
        FreeResult();
        if (type == EXEC_TYPE::EXEC_TYPE_USE)
            m_pData->res = mysql_use_result(m_pData->mysql);
        else
            m_pData->res = mysql_store_result(m_pData->mysql);
        m_pData->col_count = mysql_num_fields(m_pData->res);
        return false;
    }
    unsigned long LXMysql::Columns()
    {
        if (!m_pData || !m_pData->mysql)
            return 0;
        return m_pData->col_count;
    }
    LXMysql::uint64_t LXMysql::Rows()
    {
        if (!m_pData || !m_pData->res)
            return 0;
        return mysql_num_rows(m_pData->res);
    }
    std::string LXMysql::Value(int index)
    {
        if (!m_pData || !m_pData->row || !m_pData->lengths)
            return "";
        if (index < 0 || m_pData->col_count <= 0 || index >= m_pData->col_count)
        {
            return "";
        }
        return std::string(m_pData->row[index], m_pData->lengths[index]);
    }
    std::vector<LXData> LXMysql::FetchRow()
    {
        if (!m_pData || !m_pData->res)
            return std::vector<LXData>();
        if (!Next())
        {
            return std::vector<LXData>();
        }
        std::vector<LXData> rows;
        rows.resize(m_pData->col_count);
        for (int i = 0; i < m_pData->col_count; i++)
        {
            rows[i].data = m_pData->row[i];
            rows[i].size = m_pData->lengths[i];
        }
        return rows;
    }

    std::string LXMysql::GetInsertSql(const XDATA &data, const char *table)
    {
        // 检查传入的表名是否为空，或者data是否为空，如果为空，则返回空字符串
        if (!table || data.begin() == data.end())
            return "";

        // 初始化SQL语句，开始构造INSERT语句的表名部分
        std::string sql = std::string("INSERT INTO ") + table + "(";

        // 遍历data，构造SQL语句中的字段名部分
        for (auto it = data.begin(); it != data.end(); it++)
        {
            sql += "`";       // 字段名加反引号，以避免字段名与MySQL关键字冲突
            sql += it->first; // 添加字段名
            sql += "`";
            sql += ","; // 字段名之间用逗号分隔
        }

        // 将最后一个逗号替换为闭括号，完成字段名部分的构造
        *sql.rbegin() = ')';

        // 开始构造values部分，即字段对应的值
        sql += " values(";
        for (auto it = data.begin(); it != data.end(); it++)
        {
            sql += "'";                                           // 值部分用单引号包围，适用于字符串类型的值
            sql += std::string(it->second.data, it->second.size); // 添加值
            sql += "'";
            sql += ","; // 值之间用逗号分隔
        }

        // 将最后一个逗号替换为闭括号，完成values部分的构造
        *sql.rbegin() = ')';

        // 返回构造完成的SQL语句
        return sql;
    }

    bool LXMysql::Insert(const XDATA &data, const char *table)
    {
        // 检查数据有效性：确保数据指针、MySQL连接和表名均有效
        if (!m_pData || !m_pData->mysql || !table)
            return false;

        // 生成插入数据的SQL语句
        std::string sql = GetInsertSql(data, table);

        // 检查SQL语句是否成功生成
        if (sql.empty())
            return false;

        // 执行SQL语句
        if (mysql_real_query(m_pData->mysql, sql.data(), sql.size()))
            return false;

        // 获取受影响的行数
        int num = mysql_affected_rows(m_pData->mysql);

        // 检查受影响的行数是否大于0，以确定插入操作是否成功
        if (num <= 0)
            return false;

        // 插入成功
        return true;
    }

    bool LXMysql::InsertBinary1(const XDATA &data, const char *table, const char **err_str)
    {
        // 检查输入参数的有效性
        if (!table || data.begin() == data.end() || !m_pData || !m_pData->mysql)
            return false;

        // 初始化SQL语句
        std::string sql = "";
        sql = "insert into `";
        sql += table;
        sql += "`";

        // 初始化键和值的字符串
        std::string keys = "";
        std::string vals = "";

        // 创建一个MYSQL_BIND向量用于绑定参数
        std::vector<MYSQL_BIND> bind = std::vector<MYSQL_BIND>(data.size());

        // 遍历数据容器，构造键和值的字符串以及绑定参数
        int i = 0;
        for (auto ptr = data.begin(); ptr != data.end(); ptr++)
        {
            keys += "`";
            keys += ptr->first;
            keys += "`,";

            vals += "?,";

            // 绑定参数
            bind[i].buffer = (char *)ptr->second.data;
            bind[i].buffer_length = ptr->second.size;
            bind[i].buffer_type = (enum_field_types)ptr->second.type;
            i++;
        }

        // 移除最后一个逗号
        keys[keys.size() - 1] = ' ';
        vals[vals.size() - 1] = ' ';

        // 构造完整的SQL语句
        sql += "(";
        sql += keys;
        sql += ")values(";
        sql += vals;
        sql += ")";

        // 初始化MySQL语句对象
        MYSQL_STMT *stmt = mysql_stmt_init(m_pData->mysql);
        if (!stmt)
        {
            if (err_str)
                *err_str = mysql_error(m_pData->mysql);
            // std::cerr << "mysql_stmt_init failed!" << mysql_error(m_pData->mysql) << std::endl;
            return false;
        }

        // 预处理SQL语句
        if (mysql_stmt_prepare(stmt, sql.c_str(), sql.length()) != 0)
        {
            mysql_stmt_close(stmt);
            if (err_str)
                *err_str = mysql_error(m_pData->mysql);
            return false;
        }

        // 绑定参数到SQL语句
        if (mysql_stmt_bind_param(stmt, bind.data()) != 0)
        {
            mysql_stmt_close(stmt);
            if (err_str)
                *err_str = mysql_error(m_pData->mysql);
            return false;
        }

        // 执行SQL语句
        if (mysql_stmt_execute(stmt) != 0)
        {
            mysql_stmt_close(stmt);
            if (err_str)
                *err_str = mysql_error(m_pData->mysql);
            return false;
        }

        // 关闭MySQL语句对象
        mysql_stmt_close(stmt);
        return true;
    }

    // 插入二进制数据到指定的MySQL表中
    /**
     * @param data 包含要插入数据的容器，类型为XDATA
     * @param table 要插入数据的表名
     * @return 成功插入返回true，否则返回false
     *
     * 该函数将二进制数据插入到指定的MySQL表中。它首先检查输入参数的有效性，
     * 然后构造插入SQL语句，并使用MySQL C API进行预处理、绑定参数和执行SQL语句。
     */
    bool LXMysql::InsertBinary(const XDATA &data, const char *table)
    {
        // 检查输入参数的有效性
        if (!table || data.begin() == data.end() || !m_pData || !m_pData->mysql)
            return false;

        // 初始化SQL语句
        std::string sql = "";
        sql = "insert into `";
        sql += table;
        sql += "`";

        // 初始化键和值的字符串
        std::string keys = "";
        std::string vals = "";

        // 创建一个MYSQL_BIND向量用于绑定参数
        std::vector<MYSQL_BIND> bind = std::vector<MYSQL_BIND>(data.size());

        // 遍历数据容器，构造键和值的字符串以及绑定参数
        int i = 0;
        for (auto ptr = data.begin(); ptr != data.end(); ptr++)
        {
            keys += "`";
            keys += ptr->first;
            keys += "`,";

            vals += "?,";

            // 绑定参数
            bind[i].buffer = (char *)ptr->second.data;
            bind[i].buffer_length = ptr->second.size;
            bind[i].buffer_type = (enum_field_types)ptr->second.type;
            i++;
        }

        // 移除最后一个逗号
        keys[keys.size() - 1] = ' ';
        vals[vals.size() - 1] = ' ';

        // 构造完整的SQL语句
        sql += "(";
        sql += keys;
        sql += ")values(";
        sql += vals;
        sql += ")";

        // 初始化MySQL语句对象
        MYSQL_STMT *stmt = mysql_stmt_init(m_pData->mysql);
        if (!stmt)
        {
            std::cerr << "mysql_stmt_init failed!" << mysql_error(m_pData->mysql) << std::endl;
            return false;
        }

        // 预处理SQL语句
        if (mysql_stmt_prepare(stmt, sql.c_str(), sql.length()) != 0)
        {
            mysql_stmt_close(stmt);
            std::cerr << "mysql_stmt_prepare failed!" << mysql_error(m_pData->mysql) << std::endl;
            return false;
        }

        // 绑定参数到SQL语句
        if (mysql_stmt_bind_param(stmt, bind.data()) != 0)
        {
            mysql_stmt_close(stmt);
            std::cerr << "mysql_stmt_bind_param failed!" << mysql_stmt_error(stmt) << std::endl;
            return false;
        }

        // 执行SQL语句
        if (mysql_stmt_execute(stmt) != 0)
        {
            mysql_stmt_close(stmt);
            std::cerr << "mysql_stmt_execute failed!\n  " << mysql_stmt_error(stmt) << std::endl;
            return false;
        }

        // 关闭MySQL语句对象
        mysql_stmt_close(stmt);
        return true;
    }

    std::string LXMysql::GetUpdateSql(const XDATA &data, const char *table, const char *where)
    {
        if (data.empty() || !table || !where || !m_pData || !m_pData->mysql)
        {
            return std::string();
        }
        std::string sql = "update ";
        sql += table;
        sql += " set ";
        for (auto &it : data)
        {
            sql += "`" + it.first + "`='" + it.second.data + "',";
        }
        *sql.rbegin() = ' ';
        sql += where;
        return sql;
    }
    int LXMysql::Update(const XDATA &data, const std::string &table, const std::string &where)
    {
        if (data.empty() || table.empty() || !m_pData || !m_pData->mysql)
            return -1;
        std::string sql = GetUpdateSql(data, table, where);
        if (sql.empty())
            return -1;
        if (!Query(sql))
            return -1;
        return mysql_affected_rows(m_pData->mysql);
    }
    int LXMysql::UpdateBinary(const XDATA &data, const char *table, const char *where)
    {
        if (data.empty() || !table || !where || !m_pData || !m_pData->mysql)
        {
            return -1;
        }
        std::string sql = "UPDATE `" + std::string(table) + "` SET";
        std::vector<MYSQL_BIND> binds(data.size());
        int i = 0;
        for (auto it = data.begin(); it != data.end(); ++it, i++)
        {
            sql += " `" + it->first + "` = ?,";
            binds[i].buffer_type = (enum_field_types)it->second.type;
            auto s = it->second.data;
            binds[i].buffer = (void *)it->second.data;
            binds[i].buffer_length = it->second.size;
        }
        *sql.rbegin() = ' ';
        sql += where;

        MYSQL_STMT *stmt = mysql_stmt_init(m_pData->mysql);
        if (!stmt)
        {
            return -1;
        }
        if (mysql_stmt_prepare(stmt, sql.c_str(), sql.size()))
        {
            mysql_stmt_close(stmt);
            return -1;
        }
        if (mysql_stmt_bind_param(stmt, binds.data()))
        {
            mysql_stmt_close(stmt);
            return -1;
        }
        if (mysql_stmt_execute(stmt))
        {
            mysql_stmt_close(stmt);
            return -1;
        }
        int count = mysql_stmt_affected_rows(stmt);
        mysql_stmt_close(stmt);
        return count;
    }
    XROWS LXMysql::GetResult(const char *sql)
    {
        FreeResult();
        XROWS rows;
        if (!Query(sql))
            return rows;
        if (!StoreResult())
            return rows;
        for (;;)
        {
            auto row = FetchRow();
            if (row.empty())
                break;
            rows.push_back(row);
        }
        return rows;
    }
    // 查询函数，用于执行SQL查询
    /**
     * @param sql 要执行的SQL查询语句
     * @param len SQL查询语句的长度
     * @return bool 查询是否成功
     *
     * 此函数首先检查传入的MySQL连接和SQL查询语句是否有效，如果无效则返回false。
     * 然后根据传入的长度参数选择合适的查询函数：如果长度小于等于0，则使用mysql_query函数；
     * 否则使用mysql_real_query函数。最后根据查询函数的返回值判断查询是否成功，并返回相应的布尔值。
     */
    bool LXMysql::Query(const char *sql, size_t len)
    {
        // 检查MySQL连接和SQL查询语句是否有效
        if (!m_pData || !m_pData->mysql || !sql)
            return false;

        int ret = 0;

        // 根据长度选择合适的查询函数
        if (len <= 0)
            ret = mysql_query(m_pData->mysql, sql);
        else
            ret = mysql_real_query(m_pData->mysql, sql, len);

        // 根据查询函数的返回值判断查询是否成功
        return ret == 0;
    }
    bool LXMysql::Query(std::string sql)
    {
        if (!m_pData || !m_pData->mysql || !sql.length())
            return false;
        return 0 == mysql_real_query(m_pData->mysql, sql.data(), sql.size());
    }
    bool LXMysql::StoreResult()
    {
        if (!m_pData || !m_pData->mysql)
            return false;
        FreeResult();
        m_pData->res = mysql_store_result(m_pData->mysql);
        m_pData->col_count = mysql_num_fields(m_pData->res);
        return m_pData->res;
    }
    bool LXMysql::UseResult()
    {
        if (!m_pData || !m_pData->mysql)
            return false;
        FreeResult();
        m_pData->res = mysql_use_result(m_pData->mysql);
        m_pData->col_count = mysql_num_fields(m_pData->res);
        return m_pData->res;
    }
    void LXMysql::FreeResult()
    {
        if (!m_pData)
            return;
        if (m_pData->res)
        {
            mysql_free_result(m_pData->res);
            m_pData->res = nullptr;
        }
        m_pData->col_count = 0;
        m_pData->lengths = nullptr;
        m_pData->row = nullptr;
    }
    bool LXMysql::SetReconnect(bool isreconnect)
    {
        return SetOption(LX_OPT::LX_OPT_RECONNECT, (char *)&isreconnect);
    }
} // namespace LX
